package com.xingchi.tornado.mybatisplus.plugins;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 查询结果解密处理器
 * 用于自动解密从数据库查询的加密字段
 *
 * @author xingchi
 * @date 2024/7/23 15:30
 */
@Slf4j
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptResultSetHandler implements Interceptor {

    /**
     * 加密字段缓存，避免重复反射获取
     */
    private static final Map<Class<?>, List<Field>> ENCRYPT_FIELD_CACHE = new ConcurrentHashMap<>();

    /**
     * 加密算法实现
     */
    private final EncryptDecryptInterceptor.IEncryptor encryptor;

    /**
     * 是否自动解密响应数据
     */
    private final boolean autoDecryptResponse;

    public DecryptResultSetHandler(EncryptDecryptInterceptor.IEncryptor encryptor, boolean autoDecryptResponse) {
        this.encryptor = encryptor;
        this.autoDecryptResponse = autoDecryptResponse;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (!autoDecryptResponse) {
            return invocation.proceed();
        }

        Object resultObject = invocation.proceed();
        if (resultObject == null) {
            return null;
        }

        // 处理返回结果
        return processResultObject(resultObject);
    }

    /**
     * 处理返回结果对象
     */
    private Object processResultObject(Object resultObject) {
        if (resultObject instanceof Collection) {
            // 处理集合类型结果
            Collection<?> collection = (Collection<?>) resultObject;
            for (Object obj : collection) {
                decryptObject(obj);
            }
        } else if (resultObject instanceof Map) {
            // 处理Map类型结果
            Map<?, ?> map = (Map<?, ?>) resultObject;
            for (Object value : map.values()) {
                decryptObject(value);
            }
        } else {
            // 处理单个对象
            decryptObject(resultObject);
        }
        return resultObject;
    }

    /**
     * 解密单个对象
     */
    private void decryptObject(Object object) {
        if (object == null) {
            return;
        }

        List<Field> fields = getEncryptFields(object.getClass());
        if (fields.isEmpty()) {
            return;
        }

        for (Field field : fields) {
            try {
                Object fieldValue = field.get(object);
                if (fieldValue instanceof String && !StringUtils.isEmpty(fieldValue)) {
                    String encryptedValue = (String) fieldValue;
                    String decryptedValue = encryptor.decrypt(encryptedValue);
                    field.set(object, decryptedValue);
                }
            } catch (Exception e) {
                log.error("解密字段时发生异常: {}", field.getName(), e);
            }
        }
    }

    /**
     * 获取需要加密的字段
     */
    private List<Field> getEncryptFields(Class<?> clazz) {
        // 先从缓存中获取
        List<Field> fields = ENCRYPT_FIELD_CACHE.getOrDefault(clazz, new ArrayList<>());
        if (!CollectionUtils.isEmpty(fields)) {
            return fields;
        }

        // 如果缓存中没有，则扫描字段
        ReflectionUtils.doWithFields(clazz, field -> {
            if (field.isAnnotationPresent(Encrypt.class)) {
                field.setAccessible(true);
                fields.add(field);
            }
        });

        // 放入缓存
        ENCRYPT_FIELD_CACHE.put(clazz, fields);
        return fields;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以从配置中读取属性
    }
}
